<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="app"></div>

  <script>
    /**
     * 已知`单文件组件`、`函数式组件`以及`虚拟dom`
     * 要求把这个虚拟dom渲染到document
     */

     /** 虚拟节点类型标志 */
    const ShapeFlags = {
      // html原生元素
      ELEMENT: 1,
      // 函数式组件
      FUNCTIONAL_COMPONENT: 1 << 1,
      // SFC
      STATEFUL_COMPONENT: 1 << 2,
    }

    /** 单文件组件.vue编译后的 */
    const SingleFileComponent = {
      render() {
        return h('h3', null, '单文件组件')
      }
    }

    /** 函数式组件 */
    function FunctionComponent() {
      return h('button', null, '函数式组件');
    }

    /** 虚拟dom */
    const vdom = h('div', null, [
      h('span', null, 'hello world'),
      h('p', null, 'hello world2'),
      h(FunctionComponent, null, ''),
      h(SingleFileComponent, null, ''),
    ]);

    /** 渲染虚拟dom到#app节点 */
    mount(vdom, document.getElementById('app'));    

    /**
     * h函数，返回虚拟dom
     */
    function h(tagName, props, children) {
      let shapeFlag = ShapeFlags.ELEMENT;

      const tagNameType = Object.prototype.toString.apply(tagName);

      if (tagNameType === '[object Function]') {
        shapeFlag = ShapeFlags.FUNCTIONAL_COMPONENT;
      } else if (tagNameType === '[object Object]') {
        shapeFlag = ShapeFlags.STATEFUL_COMPONENT;
      }

      return {
        tag: tagName,
        shapeFlag,
        children: children
      }
    }

    /** 
     * 渲染虚拟节点到container
     */
    function mount(vNode, container) {
      if (vNode.shapeFlag & ShapeFlags.FUNCTIONAL_COMPONENT) {
        mountFunctionComponent(vNode, container);
      } else if (vNode.shapeFlag & ShapeFlags.ELEMENT) {
        mountElement(vNode, container);
      } else if (vNode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
        mountSingleFileComponent(vNode, container);
      }
    }

    /** 
     * 渲染html元素
     */
    function mountElement(vNode, container) {
      const el = document.createElement(vNode.tag);
      const childrenType = Object.prototype.toString.apply(vNode.children);
      if (childrenType === '[object String]') {
        el.innerText = vNode.children;
      } else if (childrenType === '[object Array]') {
        vNode.children.forEach(vNode => {
          mount(vNode, el);
        });
      }
      vNode.el = el;
      container.appendChild(el);
    }

    /** 
     * 渲染函数式组件
     */
    function mountFunctionComponent(vNode, container) {
      const compVNode = vNode.tag();
      mount(compVNode, container);
      container.appendChild(compVNode.el);
    }

    /**
     *  渲染单文件组件
     */
    function mountSingleFileComponent(vNode, container) {
      const compVNode = vNode.tag.render();
      mount(compVNode, container);
      container.appendChild(compVNode.el);
    }
    
  </script>
</body>
</html>